
#include <kchashdb.h>
#include <kctreedb.h>
#include <kcutil.h>

#include <iostream>
#include <sstream>

#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>

#include <boost/filesystem.hpp>


#include "bocat/common/ini.hpp"
#include "bocat/common/logging.hpp"

#include "binindex.hpp"

namespace	fekcab	{


#define	DEFAULT_LMEMB	1024
#define	DEFAULT_NMEMB	2046

#define	DEFAULT_LCNUM	8192
#define	DEFAULT_NCNUM	2048

#define	DEFAULT_MSIZ	67108864

#ifndef	UINT8_MAX
#define	UINT8_MAX	((uint8_t)255U)
#endif


///////////////////////////////////////////////////////////////////////
//

using namespace kyotocabinet;


#define INFO(ARGS...)   logger_p->info(__LINE__,__FILE__,ARGS)


// convert a number into the string with the decimal unit
inline std::string unitnumstr(int64_t num) {
  if (num >= pow(1000, 6)) {
    return kyotocabinet::strprintf("%.3Lf quintillion", (long double)num / pow(1000, 6));
  } else if (num >= pow(1000, 5)) {
    return kyotocabinet::strprintf("%.3Lf quadrillion", (long double)num / pow(1000, 5));
  } else if (num >= pow(1000, 4)) {
    return kyotocabinet::strprintf("%.3Lf trillion", (long double)num / pow(1000, 4));
  } else if (num >= pow(1000, 3)) {
    return kyotocabinet::strprintf("%.3Lf billion", (long double)num / pow(1000, 3));
  } else if (num >= pow(1000, 2)) {
    return kyotocabinet::strprintf("%.3Lf million", (long double)num / pow(1000, 2));
  } else if (num >= pow(1000, 1)) {
    return kyotocabinet::strprintf("%.3Lf thousand", (long double)num / pow(1000, 1));
  }
  return kyotocabinet::strprintf("%lld", (long long)num);
}


// convert a number into the string with the byte unit
inline std::string unitnumstrbyte(int64_t num) {
  if ((unsigned long long)num >= 1ULL << 60) {
    return kyotocabinet::strprintf("%.3Lf EiB", (long double)num / (1ULL << 60));
  } else if ((unsigned long long)num >= 1ULL << 50) {
    return kyotocabinet::strprintf("%.3Lf PiB", (long double)num / (1ULL << 50));
  } else if ((unsigned long long)num >= 1ULL << 40) {
    return kyotocabinet::strprintf("%.3Lf TiB", (long double)num / (1ULL << 40));
  } else if ((unsigned long long)num >= 1ULL << 30) {
    return kyotocabinet::strprintf("%.3Lf GiB", (long double)num / (1ULL << 30));
  } else if ((unsigned long long)num >= 1ULL << 20) {
    return kyotocabinet::strprintf("%.3Lf MiB", (long double)num / (1ULL << 20));
  } else if ((unsigned long long)num >= 1ULL << 10) {
    return kyotocabinet::strprintf("%.3Lf KiB", (long double)num / (1ULL << 10));
  }
  return kyotocabinet::strprintf("%lld B", (long long)num);
}


///////////////////////////////////////////////////////////////////////
//


class   KCHIndex	:	public KCAIndex
{
	typedef HashDB  	db_type;
	typedef HashDB * 	dbp_type;

public  :
    KCHIndex( const char* ini_filename, const char* ini_section )
		:	
			_dbp( NULL )
	{
		parseConfigure( ini_filename, ini_section );

		_dbp = new db_type();

		_db_opened = false;

		bocat::common::Logger*	logger_p = bocat::common::getLogger();
		logger_p->info( __LINE__, __FILE__, "open KyotoCabinet %s ...", _db_file.c_str() );
		logger_p->info( __LINE__, __FILE__, "  KyotoCabinet Version : %s (%d.%d:%d) on %s",
						   kyotocabinet::VERSION, 
						   kyotocabinet::LIBVER, kyotocabinet::LIBREV, kyotocabinet::FMTVER, 
						   kyotocabinet::SYSNAME);
        logger_p->info( __LINE__, __FILE__, "  kch.bnum = %lld", _bnum );
        logger_p->info( __LINE__, __FILE__, "  kch.msiz = %lld", _msiz );

		_dbp->tune_buckets( _bnum );
		_dbp->tune_map(_msiz);

		if ( !_dbp->open(_db_file.c_str(), HashDB::OWRITER | HashDB::OCREATE ) ){
			std::ostringstream	msg;
			msg << "fail to open KyotoCabinet '" << _db_file << "' -- " << _dbp->error().name();

			throw std::runtime_error( msg.str() );
		}

        check();

		_db_opened = true;
	}

    virtual	~KCHIndex()
	{
		if ( _db_opened ) {
			_dbp->close();

			_db_opened = false;
		}

		if ( _dbp ) {
			delete _dbp;
			_dbp = NULL;
		}
	}

private :

    void    check( void ) 
    {
        std::map<std::string, std::string> status;

        _dbp->status(&status);

		bocat::common::Logger*	logger_p = bocat::common::getLogger();

        status["fbpnum_used"] = "";
        status["bnum_used"] = "";
        status["opaque"] = "";
        uint32_t type = kyotocabinet::atoi(status["realtype"].c_str());
        INFO("    type: %s (type=0x%02X) (%s)",
                status["type"].c_str(), type, kyotocabinet::DB::typestring(type));
        uint32_t chksum = kyotocabinet::atoi(status["chksum"].c_str());
        INFO("    format version: %s (libver=%s.%s) (chksum=0x%02X)", status["fmtver"].c_str(),
                status["libver"].c_str(), status["librev"].c_str(), chksum);
        INFO("    path: %s", status["path"].c_str());

        char    buf[ 256 ];
        buf[0] = 0;
        int32_t flags = kyotocabinet::atoi(status["flags"].c_str());
        if (kyotocabinet::atoi(status["recovered"]) > 0) 
            sprintf(buf, "(recovered)");
        if (kyotocabinet::atoi(status["reorganized"]) > 0) 
            sprintf(buf, "(reorganized)");
        if (flags & kyotocabinet::HashDB::FOPEN) 
            INFO("    status flags : open  (flags=%d) %s", flags, buf);
        if (flags & kyotocabinet::HashDB::FFATAL) 
            INFO("    status flags : fatal (flags=%d) %s", flags, buf);

        int32_t apow = kyotocabinet::atoi(status["apow"].c_str());
        INFO("    alignment: %d (apow=%d)", 1 << apow, apow);
        int32_t fpow = kyotocabinet::atoi(status["fpow"].c_str());
        int32_t fbpnum = fpow > 0 ? 1 << fpow : 0;
        int32_t fbpused = kyotocabinet::atoi(status["fbpnum_used"].c_str());
        int64_t frgcnt = kyotocabinet::atoi(status["frgcnt"].c_str());
        INFO("    free block pool: %d (fpow=%d) (used=%d) (frg=%lld)",
                fbpnum, fpow, fbpused, (long long)frgcnt);

        int32_t opts = kyotocabinet::atoi(status["opts"].c_str());
        if (opts & kyotocabinet::HashDB::TSMALL) 
            INFO("    options: small    (opts=%d)", opts);
        if (opts & kyotocabinet::HashDB::TLINEAR) 
            INFO("    options: linear   (opts=%d)", opts);
        if (opts & kyotocabinet::HashDB::TCOMPRESS)
            INFO("    options: compress (opts=%d)", opts);

        const char* opaque = status["opaque"].c_str();
        if (std::count(opaque, opaque + 16, 0) != 16) {
          for (int32_t i = 0; i < 16; i++) {
            sprintf(buf + i*3, " %02X", ((unsigned char*)opaque)[i]);
          }
        } else {
          sprintf(buf, " 0");
        }
        INFO("    opaque: %s", buf);

        int64_t bnum = kyotocabinet::atoi(status["bnum"].c_str());
        int64_t bnumused = kyotocabinet::atoi(status["bnum_used"].c_str());
        int64_t count = kyotocabinet::atoi(status["count"].c_str());
        double load = 0;
        if (count > 0 && bnumused > 0) {
          load = (double)count / bnumused;
          if (!(opts & kyotocabinet::HashDB::TLINEAR)) load = log2(load + 1);
        }
        INFO("    buckets: %lld (used=%lld) (load=%.2f)",
                (long long)bnum, (long long)bnumused, load);
        std::string cntstr = unitnumstr(count);
        INFO("    count: %lld (%s)", count, cntstr.c_str());

        int64_t size = kyotocabinet::atoi(status["size"].c_str());
        int64_t msiz = kyotocabinet::atoi(status["msiz"].c_str());
        int64_t realsize = kyotocabinet::atoi(status["realsize"].c_str());
        std::string sizestr = unitnumstrbyte(size);
        if (size != realsize) 
            INFO("    size: %lld (%s) (map=%lld) (gap=%lld)", 
                    size, sizestr.c_str(), (long long)msiz, (long long)(realsize - size));
        else
            INFO("    size: %lld (%s) (map=%lld)", size, sizestr.c_str(), (long long)msiz);
    }


public  :
    int put( const std::string& skey,
             const std::string&  data )
	{
		if ( !_dbp->set( skey, data ) ) {
			std::ostringstream	msg;

			msg << "fail to insert data -- " << _dbp->error().name();

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

    int get( const std::string& skey,
                   std::string& data )
	{
		std::string*	p = _dbp->get( skey );
		if ( NULL == p ) {
			return	0;
		}
		
		data = *p;
		delete p;

		return	0;
	}


    int erase( const std::string& skey )
	{
		if ( !_dbp->remove( skey ) ) {
			std::ostringstream	msg;

			msg << "fail to delete key -- " << _dbp->error().name();

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

private	:
	void	parseConfigure( const char* ini_filename, const char* ini_section )
	{
		bocat::common::INIFile	ini( ini_filename );

		std::string	SECTION( ini_section );

		_db_file = ini[ SECTION ][ "dbfile" ];
		if ( _db_file.size() < 1 ) {
			std::ostringstream	msg;
			msg << "No 'dbfile' configured in [" << SECTION << "] of " << ini_filename;

			throw std::runtime_error( msg.str() );
		}

		std::string	xmsiz = ini[ SECTION ][ "kch.msiz" ];
		if ( xmsiz.size() < 1 ) {
			_msiz = DEFAULT_MSIZ;
		}
		else {
			_msiz = boost::lexical_cast< int64_t >( xmsiz.c_str() );
		}

		std::string	bnum = ini[ SECTION ][ "kch.bnum" ];
		if ( bnum.size() < 1 ) {
			_bnum = DEFAULT_BNUM;
		}
		else {
			_bnum = boost::lexical_cast< int64_t >( bnum.c_str() );
		}


	}


private :
    std::string _db_file;

    int64_t     _bnum;
	int64_t		_msiz;

    dbp_type    _dbp;

    bool        _db_opened;

};	//// class KCHIndex


///////////////////////////////////////////////////////////////////////
//


class   KCBIndex	:	public KCAIndex
{
	typedef TreeDB  	db_type;
	typedef TreeDB * 	dbp_type;

public  :
    KCBIndex( const char* ini_filename, const char* ini_section )
		:	
			_dbp( NULL )
	{
		parseConfigure( ini_filename, ini_section );

		_dbp = new db_type();

		_db_opened = false;

		bocat::common::Logger*	logger_p = bocat::common::getLogger();
		logger_p->info( __LINE__, __FILE__, "open KyotoCabinet %s ...", _db_file.c_str() );
		logger_p->info( __LINE__, __FILE__, "  KyotoCabinet Version : %s (%d.%d:%d) on %s",
						   kyotocabinet::VERSION, 
						   kyotocabinet::LIBVER, kyotocabinet::LIBREV, kyotocabinet::FMTVER, 
						   kyotocabinet::SYSNAME);
        logger_p->info( __LINE__, __FILE__, "  kch.bnum = %lld", _bnum );
        logger_p->info( __LINE__, __FILE__, "  kch.msiz = %lld", _msiz );

		_dbp->tune_buckets( _bnum );
		_dbp->tune_map(_msiz);

		if ( !_dbp->open(_db_file.c_str(), TreeDB::OWRITER | TreeDB::OCREATE ) ){
			std::ostringstream	msg;
			msg << "fail to open KyotoCabinet '" << _db_file << "' -- " << _dbp->error().name();

			throw std::runtime_error( msg.str() );
		}

        check();

		_db_opened = true;
	}

    virtual	~KCBIndex()
	{
		if ( _db_opened ) {
			_dbp->close();

			_db_opened = false;
		}

		if ( _dbp ) {
			delete _dbp;
			_dbp = NULL;
		}
	}

private :

    void    check( void ) 
    {
        std::map<std::string, std::string> status;

        _dbp->status(&status);

		bocat::common::Logger*	logger_p = bocat::common::getLogger();

        status["fbpnum_used"] = "";
        status["bnum_used"] = "";
        status["opaque"] = "";
        status["cusage_lcnt"] = "";
        status["cusage_lsiz"] = "";
        status["cusage_icnt"] = "";
        status["cusage_isiz"] = "";
        status["tree_level"] = "";

        uint32_t type = kyotocabinet::atoi(status["realtype"].c_str());
        INFO("    type: %s (type=0x%02X) (%s)",
                status["type"].c_str(), type, kyotocabinet::DB::typestring(type));
        uint32_t chksum = kyotocabinet::atoi(status["chksum"].c_str());
        INFO("    format version: %s (libver=%s.%s) (chksum=0x%02X)", status["fmtver"].c_str(),
                status["libver"].c_str(), status["librev"].c_str(), chksum);
        INFO("    path: %s", status["path"].c_str());

        char    buf[ 256 ];
        buf[0] = 0;
        int32_t flags = kyotocabinet::atoi(status["flags"].c_str());
        if (kyotocabinet::atoi(status["recovered"]) > 0) 
            sprintf(buf, "(recovered)");
        if (kyotocabinet::atoi(status["reorganized"]) > 0) 
            sprintf(buf, "(reorganized)");
        if (flags & kyotocabinet::TreeDB::FOPEN) 
            INFO("    status flags : open  (flags=%d) %s", flags, buf);
        if (flags & kyotocabinet::TreeDB::FFATAL) 
            INFO("    status flags : fatal (flags=%d) %s", flags, buf);

        int32_t apow = kyotocabinet::atoi(status["apow"].c_str());
        INFO("    alignment: %d (apow=%d)", 1 << apow, apow);
        int32_t fpow = kyotocabinet::atoi(status["fpow"].c_str());
        int32_t fbpnum = fpow > 0 ? 1 << fpow : 0;
        int32_t fbpused = kyotocabinet::atoi(status["fbpnum_used"].c_str());
        int64_t frgcnt = kyotocabinet::atoi(status["frgcnt"].c_str());
        INFO("    free block pool: %d (fpow=%d) (used=%d) (frg=%lld)",
                fbpnum, fpow, fbpused, (long long)frgcnt);

        int32_t opts = kyotocabinet::atoi(status["opts"].c_str());
        if (opts & kyotocabinet::TreeDB::TSMALL) 
            INFO("    options: small    (opts=%d)", opts);
        if (opts & kyotocabinet::TreeDB::TLINEAR) 
            INFO("    options: linear   (opts=%d)", opts);
        if (opts & kyotocabinet::TreeDB::TCOMPRESS)
            INFO("    options: compress (opts=%d)", opts);

        INFO("    comparator: %s", status["rcomp"].c_str());

        const char* opaque = status["opaque"].c_str();
        if (std::count(opaque, opaque + 16, 0) != 16) {
          for (int32_t i = 0; i < 16; i++) {
            sprintf(buf + i*3, " %02X", ((unsigned char*)opaque)[i]);
          }
        } else {
          sprintf(buf, " 0");
        }
        INFO("    opaque: %s", buf);

        int64_t bnum = kyotocabinet::atoi(status["bnum"].c_str());
        int64_t bnumused = kyotocabinet::atoi(status["bnum_used"].c_str());
        int64_t count = kyotocabinet::atoi(status["count"].c_str());
        int64_t mcnt = 1;
        int64_t lcnt = kyotocabinet::atoi(status["lcnt"].c_str());
        int64_t icnt = kyotocabinet::atoi(status["icnt"].c_str());
        int64_t ncnt = mcnt + lcnt + icnt;
        int32_t tlevel = kyotocabinet::atoi(status["tree_level"].c_str());
        int32_t psiz = kyotocabinet::atoi(status["psiz"].c_str());
        double load = 1;
        if (ncnt > 0 && bnumused > 0) {
          load = (double)ncnt / bnumused;
          if (!(opts & kyotocabinet::TreeDB::TLINEAR)) load = log2(load + 1);
        }
        INFO("    buckets: %lld (used=%lld) (load=%.2f)",
                (long long)bnum, (long long)bnumused, load);
        INFO("    nodes: %lld (meta=%lld) (leaf=%lld) (inner=%lld) (level=%d) (psiz=%d)",
                (long long)ncnt, (long long)mcnt, (long long)lcnt, (long long)icnt, tlevel, psiz);
        int64_t pccap = kyotocabinet::atoi(status["pccap"].c_str());
        int64_t cusage = kyotocabinet::atoi(status["cusage"].c_str());
        int64_t culcnt = kyotocabinet::atoi(status["cusage_lcnt"].c_str());
        int64_t culsiz = kyotocabinet::atoi(status["cusage_lsiz"].c_str());
        int64_t cuicnt = kyotocabinet::atoi(status["cusage_icnt"].c_str());
        int64_t cuisiz = kyotocabinet::atoi(status["cusage_isiz"].c_str());
        INFO("    cache: %lld (cap=%lld) (ratio=%.2f) (leaf=%lld:%lld) (inner=%lld:%lld)",
                (long long)cusage, (long long)pccap, (double)cusage / pccap,
                (long long)culsiz, (long long)culcnt, (long long)cuisiz, (long long)cuicnt);

        std::string cntstr = unitnumstr(count);
        INFO("    count: %lld (%s)", count, cntstr.c_str());

        int64_t size = kyotocabinet::atoi(status["size"].c_str());
        int64_t msiz = kyotocabinet::atoi(status["msiz"].c_str());
        int64_t realsize = kyotocabinet::atoi(status["realsize"].c_str());
        std::string sizestr = unitnumstrbyte(size);
        if (size != realsize) 
            INFO("    size: %lld (%s) (map=%lld) (gap=%lld)", 
                    size, sizestr.c_str(), (long long)msiz, (long long)(realsize - size));
        else
            INFO("    size: %lld (%s) (map=%lld)", size, sizestr.c_str(), (long long)msiz);
    }


public  :
    int put( const std::string& skey,
             const std::string&  data )
	{
		if ( !_dbp->set( skey, data ) ) {
			std::ostringstream	msg;

			msg << "fail to insert data -- " << _dbp->error().name();

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

    int get( const std::string& skey,
                   std::string& data )
	{
		std::string*	p = _dbp->get( skey );
		if ( NULL == p ) {
			return	0;
		}
		
		data = *p;
		delete p;

		return	0;
	}


    int erase( const std::string& skey )
	{
		if ( !_dbp->remove( skey ) ) {
			std::ostringstream	msg;

			msg << "fail to delete key -- " << _dbp->error().name();

			throw std::runtime_error( msg.str() );
		}

		return	0;
	}

private	:
	void	parseConfigure( const char* ini_filename, const char* ini_section )
	{
		bocat::common::INIFile	ini( ini_filename );

		std::string	SECTION( ini_section );

		_db_file = ini[ SECTION ][ "dbfile" ];
		if ( _db_file.size() < 1 ) {
			std::ostringstream	msg;
			msg << "No 'dbfile' configured in [" << SECTION << "] of " << ini_filename;

			throw std::runtime_error( msg.str() );
		}

		std::string	xmsiz = ini[ SECTION ][ "kch.msiz" ];
		if ( xmsiz.size() < 1 ) {
			_msiz = DEFAULT_MSIZ;
		}
		else {
			_msiz = boost::lexical_cast< int64_t >( xmsiz.c_str() );
		}

		std::string	bnum = ini[ SECTION ][ "kch.bnum" ];
		if ( bnum.size() < 1 ) {
			_bnum = DEFAULT_BNUM;
		}
		else {
			_bnum = boost::lexical_cast< int64_t >( bnum.c_str() );
		}


	}


private :
    std::string _db_file;

    int64_t     _bnum;
	int64_t		_msiz;

    dbp_type    _dbp;

    bool        _db_opened;

};	//// class KCBIndex


///////////////////////////////////////////////////////////////////////
//


const std::string   DB_ENGINE_BTREE( "btree" );
const std::string   DB_ENGINE_HASH(  "hash"  );


BinIndex::BinIndex( const char* ini_filename, const char* ini_section )
		:	_db_p( NULL )
{
    parseConfigure( ini_filename, ini_section );

	// create dbhome
	boost::filesystem::path	dbhome_path( _db_home );
	if ( !boost::filesystem::exists( dbhome_path ) ) {
		try {
			boost::filesystem::create_directories( dbhome_path );
		}
		catch( std::exception& e ) {
			std::ostringstream	msg;
			msg << "fail to create dbhome " << _db_home << " -- " << e.what();
			throw std::runtime_error( msg.str() );
		}
	}

    if ( DB_ENGINE_BTREE == _engine ) {
        _db_p = new KCBIndex( ini_filename, ini_section );
    }
    else if ( DB_ENGINE_HASH == _engine ) {
        _db_p = new KCHIndex( ini_filename, ini_section );
    }
}

BinIndex::~BinIndex()
{
	if ( NULL != _db_p ) {
		delete _db_p;

		_db_p = NULL;
	}

    //closeAllTables();
}


///////////////////////////////////////////////////////////////////////
//

int 
BinIndex::put( const std::string& skey,
			   const std::string& data )
{
	return	_db_p->put( skey, data );
}

int 
BinIndex::get( const std::string& skey,
			         std::string& data )
{
	return	_db_p->get( skey, data );
}

int 
BinIndex::erase( const std::string& skey )
{
	return	_db_p->erase( skey );
}


///////////////////////////////////////////////////////////////////////
//

/*
BinIndex::dbp_type
BinIndex::openTable( const std::string& table_name )
{
    boost::mutex::scoped_lock   _scoped_lock( this->_dbp_cache_mutex );

    dbp_type    dbp;

    if ( _dbp_cache.exists( table_name ) ) { 
        dbp = _dbp_cache.get( table_name );
        return dbp;
    }   

    std::string ini_filename = _db_home + "/" + table_name + ".ini";
    boost::filesystem::path ini_filepath( ini_filename );
    if ( !boost::filesystem::exists( ini_filepath ) ) {
        // ini file does NOT exist, create it
        std::ofstream   ofs( ini_filename.c_str() );

        ofs << "[" << SECTION_NAME << "]" << std::endl
            << "engine = btree" << std::endl
            << "dbfile = " << _db_home << "/" << table_name << ".db" << std::endl
            << "tcb.bnum = 32749" << std::endl
            << "tcb.lmemb = 128" << std::endl
            << "tcb.nmemb = 256" << std::endl
            << "tcb.lcnum = 1024" << std::endl
            << "tcb.ncnum = 512" << std::endl
            << "tcb.xmsiz = 0" << std::endl
            << std::endl;

        ofs.close();
    }

    dbp = new KCBIndex( ini_filename.c_str(), SECTION_NAME );

    // put dbp into cache
    dbp_type    dbp_removed = _dbp_cache.put( table_name, dbp );
    if (dbp_removed) {
		
        delete dbp_removed;
    }

    return dbp;
}


void
BinIndex::closeAllTables()
{
    boost::mutex::scoped_lock   _scoped_lock( this->_dbp_cache_mutex );

    dbp_cache_type::Key_List    _list( _dbp_cache.get_all_keys() );
    for( dbp_cache_type::Key_List_Iter liter = _list.begin(); liter != _list.end(); liter++ ) {
        dbp_type    dbp = _dbp_cache.get( *liter, false );

        delete dbp;
    }

    _dbp_cache.clear();
}
*/

///////////////////////////////////////////////////////////////////////
//

void
BinIndex::parseConfigure( const char* ini_filename, const char* ini_section )
{
    bocat::common::INIFile	ini( ini_filename );

    std::string	SECTION( ini_section );

    _db_home = ini[ SECTION ][ "dbhome" ];
    if ( _db_home.size() < 1 ) {
        std::ostringstream	msg;
        msg << "No 'dbhome' configured in [" << SECTION << "] of " << ini_filename;

        throw std::runtime_error( msg.str() );
    }

    _engine = ini[ SECTION ][ "engine" ];
    if ( _engine.size() < 1 ) {
        std::ostringstream	msg;
        msg << "No 'engine = {" << DB_ENGINE_BTREE << "|" << DB_ENGINE_HASH << "}' configured in [" << SECTION << "] of " << ini_filename;

        throw std::runtime_error( msg.str() );
    }
    if ( (DB_ENGINE_BTREE != _engine) && (DB_ENGINE_HASH != _engine) ) {
        std::ostringstream	msg;
        msg << "ASSERT 'engine = {" << DB_ENGINE_BTREE << "|" << DB_ENGINE_HASH << "}' in [" << SECTION << "] of " << ini_filename;

        throw std::runtime_error( msg.str() );
    }
}

}	//// namespace fekcab


